/*
 * @ProjectName: 编程学习
 * @Copyright:   2019 HangZhou Ashe Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/NoteBooks/
 * @date:        2020/3/14 8:48 下午
 * @description: 本内容仅限于编程技术学习使用，转发请注明出处.
 */
package com.example.redis.lock.impl;

import com.example.redis.lock.IRedisLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *
 * </p>
 *
 * @author Helios
 * @date 2020/3/14 8:48 下午
 */
@Component
public class RedisLockImpl implements IRedisLock {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private ThreadLocal<String> lockValLocal = new ThreadLocal<>();

    private ThreadLocal<Long> asyncThreadIDLocal = new ThreadLocal<>();

    @Override
    public boolean tryLock(String key, long timeout, TimeUnit timeUnit) {
//        String uuid = UUID.randomUUID().toString();
//        lockLocal.set(uuid);
//        boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid,timeout, timeUnit);
        //可重入实现
        boolean lock = false;
        if (Objects.isNull(lockValLocal.get())) {
            String uuid = UUID.randomUUID().toString();
            lockValLocal.set(uuid);
            lock = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, timeUnit);
            //阻塞锁
            if (!lock) {
                while (true) {
                    lock = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, timeUnit);
                    if (lock) {
                        break;
                    }
                }
                //避免锁超时时间提前结束，确保业务代码运行期间，锁不会因为超时而失效，导致线程不安全 => 业务代码执行过程中，锁异步续期（使用异步是因为要避免阻塞当前线程）
                Thread thread = new Thread(() -> {
                    while (true) {
                        try {
                            Thread.sleep(10000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        stringRedisTemplate.expire(key, timeout, timeUnit);
                    }
                });
                thread.start();
                //为删除锁时需要结束续命线程, 记录线程 ID
                asyncThreadIDLocal.set(thread.getId());
            }
        } else {
            lock = true;
        }
        return lock;
    }

    @Override
    public void releaseLock(String key) {
        if (lockValLocal.get().equals(stringRedisTemplate.opsForValue().get(key))) {
            lockValLocal.remove();
            stringRedisTemplate.delete(key);
            deleteAsyncExpireThread();
        }
    }

    /**
     * <p>
     * 结束续命线程
     * </p>
     *
     * @return
     * @author Helios
     * @date
     * @params []
     */
    private void deleteAsyncExpireThread() {
        Long threadId = asyncThreadIDLocal.get();
        Thread th = null;
        if (Objects.nonNull(threadId)) {
            asyncThreadIDLocal.remove();
            ThreadGroup group = Thread.currentThread().getThreadGroup();
            while (group != null) {
                boolean stop = false;
                Thread[] threads = new Thread[(int) (group.activeCount() * 1.2)];
                int count = group.enumerate(threads, true);
                for (int i = 0; i < count; i++) {
                    if (threadId == threads[i].getId()) {
                        th = threads[i];
                        stop = true;
                        break;
                    }
                }
                if (stop) {
                    break;
                }
                group = group.getParent();
            }
            if (Objects.nonNull(th)) {
                th.interrupt();
            }
        }
    }
}
